<template>
  <div ref="threeDom"></div>
</template>

<script type="text/ecmascript-6">
import { defineComponent, onMounted, onUnmounted, ref } from "vue";
// vue3中引入Threejs,加载gltf模型,引入tweenMAX.js,实现相机运动动画
// 第一步:创建vue3.0的项目,使用npm引入threejs的安装包,编辑文章的时候使用的threejs是0.129.0版本的.
// 1.npm install three --save
// 2,引入gsap,2.0版本的叫tweenmax,升级为3.0版本后叫gsap,用法稍微做了点改变,
// 建议使用新版的gsap,
// npm安装 npm install gsap

// threejs部分
import * as THREE from "three/build/three.module.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; //控制器
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; // gltf加载器
import gsap from "gsap";
import { CSSRulePlugin } from "gsap/CSSRulePlugin";
import { Toast } from "vant";
gsap.registerPlugin(CSSRulePlugin); // 引入css插件,完成某些css动画

export default defineComponent({
  props: {
    demo: {
      type: String,
      require: true,
    },
  },
  setup(prop) {
    console.log(prop.demo, "prop.demo");
    // 2.配置threejs的环境
    const Scene = new THREE.Scene();
    const Camera = new THREE.PerspectiveCamera(
      45,
      // window.innerWidth / window.innerHeight,
      1,
      1,
      5000
    );
    const Renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true, //开启alpha
    });
    // 控制器
    const Controls = new OrbitControls(Camera, Renderer.domElement);
    //gltfLoader
    const Gltfloader = new GLTFLoader();
    // 注意:此处为threejs的DOM,需要将threejs的场景渲染进去
    const threeDom = ref(null);

    // 3.设置相机的角度与朝向,同时设置轨道控制器的朝向
    // 首页进入相机的视角,这个视角可以在三维模型中建立一个摄像机获取摄像机的坐标,如C4D,非常准确.
    // 通过坐标系调整初始模型大小
    const cameraPosition = {
      x: -180,
      y: 430,
      z: 333,
    };
    const cameraLookat = {
      x: 0,
      y: 0,
      z: 0,
    };
    // 声明一个方法传入参数可以在不同的地方调用相机
    const cameraReset = (position, lookAt, time = 1) => {
      gsap.to(Camera.position, {
        x: position.x,
        y: position.y,
        z: position.z,
        duration: time,
        ease: "power4.out",
        // onComplete: function () {
        // 这是相机运动完成的回调,可以执行其他的方法.
        // }
      });
      gsap.to(Camera.lookAt, {
        x: lookAt.x,
        y: lookAt.y,
        z: lookAt.z,
        duration: time,
        ease: "power4.out",
      });
      gsap.to(Controls.target, {
        x: lookAt.x,
        y: lookAt.y,
        z: lookAt.z,
        duration: time,
        ease: "power4.out",
      });
    };

    // 4.初始化threejs场景;并设置相机,灯光,控制器,renderer的设置
    const initThreeScene = () => {
      // 点光源
      const point = new THREE.PointLight(0xffffff, 1);
      point.position.set(10, 600, -40); // 点光源位置
      // Scene.position.set(0, 15, 0); // 场景位置
      // Scene.add(point); // 点光源添加到场景中
      // 环境光
      const ambient = new THREE.AmbientLight(0x444444, 1);
      Scene.add(ambient);
      // 辅助坐标系
      // const axesHelper = new THREE.AxesHelper(500);
      // Scene.add(axesHelper);
      // 修改相机,场景的参数
      Camera.position.set(-180, 430, 333);
      Camera.lookAt(0, 0, 0);
      Controls.target = new THREE.Vector3(0, 0, 0);
      // 使动画循环使用时阻尼或自转 意思是否有惯性
      Controls.enableDamping = true;
      // 动态阻尼系数 就是鼠标拖拽旋转灵敏度
      Controls.dampingFactor = 0.04;
      // 是否可以旋转
      Controls.enableRotate = true;
      // 是否可以缩放与速度
      Controls.enableZoom = true;
      // 设置相机距离原点的最远距离
      Controls.minDistance = 1;
      // 设置相机距离原点的最远距离
      Controls.maxDistance = 2000;
      // 是否开启右键拖拽
      Controls.enablePan = true;
      //render的相关设置
      Renderer.setPixelRatio(window.devicePixelRatio);
      // Renderer.setSize(window.innerWidth, window.innerHeight);
      Renderer.setSize(240, 240); // 设置canvas的大小
      Renderer.inputEncoding = true;
      Renderer.outputEncoding = THREE.sRGBEncoding;
      Renderer.setClearColor(0xd0d0d0, 0); // canvas背景颜色 透明度
      // 将renderer渲染进DOM里面
      threeDom.value.appendChild(Renderer.domElement);
      console.log(Controls, "Controls");
    };
    // 设置页面自适应
    const onWindowResize = () => {
      Camera.aspect = window.innerWidth / window.innerHeight;
      Camera.updateProjectionMatrix();
      Renderer.setSize(window.innerWidth, window.innerHeight);
    };
    window.addEventListener("resize", onWindowResize, false);
    //  完成以上步骤基本的场景已经配置完成

    // 5.加载gltf/glb格式的模型,经过对比各种格式(如obj,fbx),推荐大家使用gltf/glb格式的;
    Gltfloader.load(
      prop.demo,
      (gltf) => {
        Scene.add(gltf.scene);
        // 模型加载完,进行相机的初始化,传入设置的参数,模型加载为异步
        cameraReset(cameraPosition, cameraLookat);
      },
      function (xhr) {
        // 控制台查看加载进度xhr
        console.log(Math.floor((xhr.loaded / xhr.total) * 100));
      },
      function () {
        Toast("加载失败,3D文件有问题,请联系管理员");
      }
    );

    // 6 声明render函数进行循环渲染
    const Render = () => {
      requestAnimationFrame(Render);
      Controls.update(); // 轨道控制器的更新
      Renderer.clear(); // 清除画布
      Renderer.render(Scene, Camera);
    };

    // 7.在onMounted中调用方法初始化threejs场景,在onUnmounted 中释放内存,移除事件监听
    onMounted(() => {
      initThreeScene();
      Render();
    });
    onUnmounted(() => {
      Scene.traverse((e) => {
        if (e.BufferGeometry) e.BufferGeometry.dispose();
        if (e.material) {
          if (Array.isArray(e.material)) {
            e.material.forEach((m) => {
              m.dispose();
            });
          } else {
            e.material.dispose();
          }
        }
        if (e.isMesh) {
          e.remove();
        }
      });
      Scene.remove();
      Renderer.dispose();
      Renderer.content = null;
      window.removeEventListener("resize", onWindowResize, false);
    });
    // setup中将threejs的dom返回

    // 不要把threejs的scene,camera,renderer等内部函数写入vue的响应式数据（ref,reactive）
    // 8,最后在顶部引入refthreejsdom

    return {
      threeDom,
    };
  },
});
</script>

<style scoped lang="less">
</style>